from Commons import *
from Item import *
from Combat import *
from Map import AStarPathfinding
#ai classes
class LineTrackAI:#dumb zombie ai, creature looses interest when you move out of sight
    def __init__(self,creature):
        self.attachedcreature = creature
    def determineMove(self):
        ac = self.attachedcreature
        if (Game()[A_MAP][(ac.x,ac.y)].lit):#if in los
            L.line_init(ac.x,ac.y,Game()[A_PLAYER].x,Game()[A_PLAYER].y)#cast a line, and move one step on it
            xmov,ymov = L.line_step()
            xmov -= ac.x
            ymov -= ac.y
            return ac.move(xmov,ymov)#try a move
        else:
            return ac.movespeed#wait

class AStarAI:#a star pathing ai, creature will actively chase you
    def __init__(self,creature):
        self.attachedcreature = creature
        self.following = False
    def determineMove(self):
        ac = self.attachedcreature
        if (Game()[A_MAP][(ac.x,ac.y)].lit or self.following):#if in los
            self.following = True
            #do an A-Star path
            path = AStarPathfinding(ac.x,ac.y,Game()[A_PLAYER].x,Game()[A_PLAYER].y)
            xmov,ymov = L.path_walk(path,False)
            L.path_delete(path)#delete the path
            xmov -= ac.x
            ymov -= ac.y
            return ac.move(xmov,ymov)
        else:
            return ac.movespeed
    
#creature classes
class Creature:
    isPlayer = False#is the creature the player
    isWallSliding = False#is the creature wall sliding? prevent infinite recursive loops
    movespeed = 80#make all speeds high, to increase effect of diagonals
    attackspeed = 200
    attackskill = 100#attack skill - random number from 0 to attack skill to hit
    defenseskill = 100#defense skill - random number from 0 to defense skill to nullify the attack
    armor = 10#powerful, reduces damage by 0 to armor value
    damage = 9
    max_hp = 50
    letter = 'D'
    color = L.green
    descriptor = "Dragon"
    faction = F_MON
    def __init__(self, xpos=7, ypos=7):
        self.x = xpos
        self.y = ypos
        self.hp = self.max_hp
        #handle ai
        if (not self.isPlayer):#if not player
            self.attachedai = AStarAI(self)
        else:
            self.attachedai = None
        self.inventory = CreatureInventory(self)
    def place(self,xpos,ypos):#moves a creature to a particular spot (teleport)
        self.x = xpos
        self.y = ypos
        Game()[A_MAP][L_CRE].modifyObjectDisplay(self,xpos=xpos,ypos=ypos)#move the object on the map
        if (self.isPlayer):#special scripts if the moving creature is the player
            self.updateNormalView()
        Game()[A_MAP].trapEffects(self)#apply all trap effects
    def die(self):
        if (not self.isPlayer):
            #remove everything and put it on the ground
            items = self.inventory.removeEverything()
            for item in items:
                Game()[A_MAP].addItem(self.x,self.y,item)
            Game()[A_MAP][L_CRE].destroyObject(self)#remove the monster
            return
        else:#player death -- special
            addMessage("You Die!",L.red)
            updateDisplay()#update the display!
            key = L.console_wait_for_keypress(True)#wait for a keypress
            if (os.path.isfile(FILE_PATH)):#delete the save upon exiting
                os.remove(FILE_PATH)
            sys.exit(0)#quit
    def loseHealth(self,hploss):#creature effect, XXX:do scripting
        if (hploss < 0):#you can't gain health
            hploss = 0
        self.hp -= hploss
        addMessage(self.descriptor+" loses "+str(hploss)+" health.",L.white)
        if (self.hp <= 0):
            self.die()
            return True#died
        return False#didn't die
    def testMove(self,xmov,ymov):#test a possible move, for purposes of ai and such
        if (abs(xmov) > 1 or abs(ymov) > 1):
            print "Cannot use move since the parameters are bigger than one"
            raise TypeError
        newx = self.x+xmov
        newy = self.y+ymov
        if (Game()[A_MAP][(newx,newy)].block_movement):#wall
            return False
        if (Game()[A_MAP][L_CRE][(newx,newy)] != None):#creature - return true
            return True
        return True#success
    def move(self,xmov,ymov):#move
        #sanity test
        if (abs(xmov) > 1 or abs(ymov) > 1):
            print "Cannot use move since the parameters are bigger than one"
            raise TypeError
        newx = self.x+xmov
        newy = self.y+ymov
        if (Game()[A_MAP][(newx,newy)].block_movement):#wall
            #check for wall sliding before stopping
            #only the player can wall slide if he has it enabled, and moves cannot be bigger then 1,1
            if (not self.isPlayer or not WALL_SLIDE or self.isWallSliding):
                return -1
            #the wall sliding algorithm checks for blocked movement 1-3 turns to the left and right
            #of the player, and moves the player if only one of the spots is free, to that spot
            self.isWallSliding = True#we are wall sliding
            leftmove = xmov,ymov#track moves to the left and right
            rightmove = xmov,ymov
            for i in range(3):
                leftmove = turnLeft(*leftmove)
                rightmove = turnRight(*rightmove)
                if (self.testMove(*leftmove) and self.testMove(*rightmove)):#stop if both aren't blocked
                    self.isWallSliding = False
                    return -1
                if (not self.testMove(*leftmove) and self.testMove(*rightmove)):#move to right
                    self.isWallSliding = False
                    return self.move(*rightmove)
                if (self.testMove(*leftmove) and not self.testMove(*rightmove)):#move to left
                    self.isWallSliding = False
                    return self.move(*leftmove)
                #both are blocked so continue
            self.isWallSliding = False
            return -1#no solution after 3 turns
        
        if (Game()[A_MAP][L_CRE][(newx,newy)] != None):
            othercre = Game()[A_MAP][L_CRE][(newx,newy)]
            if (self.faction != othercre.faction):
                #opposing creature - attack and return true
                return combat(self,Game()[A_MAP][L_CRE][(newx,newy)])
            else:#same faction
                return -1
            
            
        #success
        self.place(newx,newy)
        return self.movespeed
    def act(self):
        movecost = self.attachedai.determineMove()
        if (movecost == -1):#invalid move, so wait
            movecost = self.movespeed
        actioncost = int(movecost)#determine the move
        return actioncost#test different speeds until it is coded elsewhere
class Player(Creature):
    isPlayer=True
    attackskill = 100
    defenseskill = 100
    armor = 0
    movespeed = 60#speed
    damagedice = 1
    damagedie = 6
    damagemodifier = 3
    script = None#script for autotravel, etc
    displaytype = S_NOR#the display type
    displayselection = None#the display selection (such as for drop) XXX: improve?
    descriptor = "Player"
    faction = F_PLA
    targetranged = False#does the player do ranged targeting?
    def __init__(self, xpos=7, ypos=7):
        Creature.__init__(self,xpos,ypos)
        self.exitGame = False
        self.hp = 150
        self.max_hp = 150
    def handleKeys(self, key):#handle the key presses
        greyMessages()#grey old messages#temporary
        if (self.displaytype == S_HEL):#help display
            if (key.c == ord('q') or key.vk == L.KEY_ESCAPE):
                self.displaytype = S_NOR#escape out of this
                self.displayselection = None#escape
                return -1
        if (self.displaytype == S_INV):#inventory display
            if (key.c == ord('q') or key.vk == L.KEY_ESCAPE):
                self.displaytype = S_NOR#escape out of this
                self.displayselection = None#escape
                return -1
            #change the display selection based on the key pressed
            if (key.c == ord('d') or key.c == ord('a') or key.c == ord('e')):
                self.displayselection = chr(key.c)#drop an item
                return -1
            #remove the appropriate item
            if (self.displayselection == 'd' and (key.c >= ord('0') and key.c <= ord('9'))):
                removeditem = self.inventory.removeItem(key.c-ord('0'))#remove the item
                if (removeditem != None):#if there is a removed item

                    self.updateRangedTargeting()
                    Game()[A_MAP].addItem(self.x,self.y,removeditem)#add it to the map
                    self.displayselection = None#get out of the drop
                    self.displaytype = S_NOR
                    return self.movespeed
                return -1
            #applying items
            if (self.displayselection == 'a' and (key.c >= ord('0') and key.c <= ord('9'))):
                tookturn = self.inventory.useItem(key.c-ord('0'))
                self.displayselection = None
                self.displaytype = S_NOR
                if (tookturn):
                    return self.movespeed
                return -1
            if (self.displayselection == 'e' and (key.c >= ord('0') and key.c <= ord('9'))):
                tookturn = self.inventory.equipItem(key.c-ord('0'))
                self.updateRangedTargeting()
                self.displayselection = None
                self.displaytype = S_NOR
                if (tookturn):
                    return self.movespeed
                return -1
        if (self.displaytype == S_MAP):#help display
            if (key.c == ord('q') or key.vk == L.KEY_ESCAPE):
                self.displaytype = S_NOR#escape out of this
                self.updateNormalView()#update the normal view
                return -1
            #scroll the map
            if (key.vk == L.KEY_LEFT):
                Game()[A_MAP].scrollx -= 1
            if (key.vk == L.KEY_RIGHT):
                Game()[A_MAP].scrollx += 1
            if (key.vk == L.KEY_UP):
                Game()[A_MAP].scrolly -= 1
            if (key.vk == L.KEY_DOWN):
                Game()[A_MAP].scrolly += 1
            return -1
        if (self.displaytype == S_NOR):
            if (key.c == ord('?')):#help
                self.displaytype = S_HEL
                return -1
            if (key.c == ord('a') or key.c == ord('d') or key.c == ord('e')):#use/drop/wield display
                self.displaytype = S_INV
                self.displayselection = chr(key.c)
                return -1
            if (key.c == ord('i')):#inventory display
                self.displaytype = S_INV
                return -1
            if (key.c == ord('X')):#map view display
                self.displaytype = S_MAP
                Game()[A_MAP].recalculateFOV(everything_unseen=True)#everything is remembered
                return -1
            if (key.c== ord('h') or key.vk == L.KEY_KP4):#left
                return Game()[A_PLAYER].move(-1,0)
            if (key.c == ord('l') or key.vk == L.KEY_KP6):#right
                return Game()[A_PLAYER].move(1,0)
            if (key.c == ord('j') or key.vk == L.KEY_KP2):#down
                return Game()[A_PLAYER].move(0,1)
            if (key.c == ord('k') or key.vk == L.KEY_KP8):#up
                return Game()[A_PLAYER].move(0,-1)
            if (key.c == ord('y') or key.vk == L.KEY_KP7):#up-left
                return Game()[A_PLAYER].move(-1,-1)
            if (key.c == ord('u') or key.vk == L.KEY_KP9):#up-right
                return Game()[A_PLAYER].move(1,-1)
            if (key.c == ord('b') or key.vk == L.KEY_KP1):#down-left
                return Game()[A_PLAYER].move(-1,1)
            if (key.c == ord('n') or key.vk == L.KEY_KP3):#down-right
                return Game()[A_PLAYER].move(1,1)
            if (key.c == ord('.') or key.vk == L.KEY_KP5):#wait
                return self.movespeed
            if (key.c == ord(',')):#pickup
                inventory = Game()[A_MAP][L_INV][(self.x,self.y)]
                if (inventory == None):#no items
                    addMessage("There are no items on the ground!",L.red)
                    return -1
                theitems = inventory.items
                if (self.inventory.inventorySpotsLeft() <= 0):#full player inventory
                    addMessage("Your inventory is full!",L.red)
                    return -1
                if (len(theitems) == 1):#immediately pickup the one item if there is only one
                    val = self.inventory.addItem(theitems[0])
                    addMessage(str(val)+" - "+theitems[0].name(),L.white)
                    Game()[A_MAP].removeItem(self.x,self.y,theitems[0])#remove it from the map

                    return self.movespeed
                #multipickup -- later
                #self.displaytype = S_PIC
                return -1
            if (key.vk == L.KEY_ESCAPE):
                self.exitGame = True
                return -1#quitting does not take a turn
            return -1#don't take a turn unless the player explicitly does something that costs one
        return -1
    #continuously reads keyboard and mouse input -- may do display updating?
    #when there is a move, return number of turns taken
    def keyAndMouseLoop(self):
        mouseavailable = False#bugfix, prevents one click from being registered as two
        while (not L.console_is_window_closed()):#while the window is not closed
            key = L.console_check_for_keypress(L.KEY_PRESSED)
            m = L.mouse_get_status()
            if (not (key.vk == L.KEY_NONE)):#if there is a key
                return self.handleKeys(key)#handle it
            if ((m.dx != 0 or m.dy != 0) and self.targetranged):#if there is a mouse change
                self.inventory[EQ_WEAPON].handleRangedTargeting(m)
                updateDisplay()
            if ((m.rbutton_pressed) and self.targetranged and mouseavailable):#mouse click
                if ((m.cx,m.cy) == (7,7)):#if it is at the player's position
                    return -1#no click
                #make the ranged projectile
                r = RangedProjectile(self.x,self.y,m.cx-7+self.x,m.cy-7+self.y)
                #add it
                Game()[A_MAP][L_RAN].addObject(r,r.x,r.y,r.character,r.color,isSave = False)
                return self.attackspeed#counts as an attack

            mouseavailable = True
        self.exitGame = True#exit
        return -1#no cost to exiting
    def updateRangedTargeting(self):#update viewing ranged targeting
        originaltargetranged = self.targetranged
        try:
            self.targetranged = self.inventory[EQ_WEAPON].canRange()#update targeting
        except AttributeError:
            self.targetranged = False
        if (not originaltargetranged and self.targetranged):#handle ranged targeting if suddenly viewing it
            m = L.mouse_get_status()
            self.inventory[EQ_WEAPON].handleRangedTargeting(m)
    def act(self):
        if (self.targetranged):#update ranged targeting before the player acts
            m = L.mouse_get_status()
            self.inventory[EQ_WEAPON].handleRangedTargeting(m)
        while (not self.exitGame):#while exitGame is not true
            updateDisplay()
            if (self.script == None):#if there is no script
                actioncost = int(self.keyAndMouseLoop())#get the actioncost from handling keys and mouse
                if (actioncost > 0):#if there is a move
                    return int(actioncost)
            else:#follow the script -- stub code to follow A* path
                actioncost = self.move(*L.path_walk(self.script,False))
                if (L.path_is_empty(self.script)):#path is done
                    self.script = None
        self.exitGame = False#so that the game does not quit when restarted
        Game().save(FILE_PATH)#save and exit
        sys.exit(0)
        return 0
    def updateNormalView(self):
        Game()[A_MAP].scrollx = self.x - 7
        Game()[A_MAP].scrolly = self.y - 7
        Game()[A_MAP].recalculateFOV()#recalc fov since the player moved
        